/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.corext.refactoring.code; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ConstructorInvocation; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.MethodRef; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.SuperConstructorInvocation; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeParameter; import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; import org.eclipse.jdt.core.dom.rewrite.ListRewrite; import org.eclipse.jdt.core.refactoring.CompilationUnitChange; import org.eclipse.jdt.core.refactoring.IJavaRefactorings; import org.eclipse.jdt.core.refactoring.descriptors.IntroduceFactoryDescriptor; import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory; import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility; import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.dom.Bindings; import org.eclipse.jdt.internal.corext.dom.ModifierRewrite; import org.eclipse.jdt.internal.corext.refactoring.Checks; import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment; import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments; import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil; import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory; import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine2; import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup; import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange; import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationStateChange; import org.eclipse.jdt.internal.corext.refactoring.codingspectator.WatchedJavaRefactoring; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.ASTCreator; import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil; import org.eclipse.jdt.internal.corext.util.JdtFlags; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.internal.corext.util.SearchUtils; import org.eclipse.jdt.ui.JavaElementLabels; import org.eclipse.jdt.internal.ui.JavaUIStatus; import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; import org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider; /** * Refactoring class that permits the substitution of a factory method for direct calls to a given * constructor. * * @author rfuhrer * @author Mohsen Vakilian, nchen - Extended WatchedRefactoring. * */ public class IntroduceFactoryRefactoring extends WatchedJavaRefactoring { private static final String ATTRIBUTE_PROTECT= "protect"; //$NON-NLS-1$ /** * The handle for the compilation unit holding the selection that was passed into this * refactoring. */ private ICompilationUnit fCUHandle; /** * The AST for the compilation unit holding the selection that was passed into this refactoring. */ private CompilationUnit fCU; /** * Handle for compilation unit in which the factory method/class/interface will be generated. */ private ICompilationUnit fFactoryUnitHandle; /** * The start of the original textual selection in effect when this refactoring was initiated. If * the refactoring was initiated from a structured selection (e.g. from the outline view), then * this refers to the textual selection that corresponds to the structured selection item. */ private int fSelectionStart; /** * The length of the original textual selection in effect when this refactoring was initiated. * If the refactoring was initiated from a structured selection (e.g. from the outline view), * then this refers to the textual selection that corresponds to the structured selection item. */ private int fSelectionLength; /** * The AST node corresponding to the user's textual selection. */ private ASTNode fSelectedNode; /** * The method binding for the selected constructor. */ private IMethodBinding fCtorBinding; /** * <code>TypeDeclaration</code> for class containing the constructor to be encapsulated. */ private AbstractTypeDeclaration fCtorOwningClass; /** * The name to be given to the generated factory method. */ private String fNewMethodName= null; /** * An array of <code>SearchResultGroup</code>'s of all call sites that refer to the constructor * signature in question. */ private SearchResultGroup[] fAllCallsTo; /** * The class that will own the factory method/class/interface. */ private AbstractTypeDeclaration fFactoryOwningClass; /** * The newly-generated factory method. */ private MethodDeclaration fFactoryMethod= null; /** * An array containing the names of the constructor's formal arguments, if available, otherwise * "arg1" ... "argN". */ private String[] fFormalArgNames= null; /** * An array of <code>ITypeBinding</code>'s that describes the types of the constructor * arguments, in order. */ private ITypeBinding[] fArgTypes; /** * True iff the given constructor has a varargs signature. */ private boolean fCtorIsVarArgs; /** * If true, change the visibility of the constructor to protected to better encapsulate it. */ private boolean fProtectConstructor= true; /** * An <code>ImportRewrite</code> that manages imports needed to satisfy newly-introduced type * references in the <code>ICompilationUnit</code> currently being rewritten during * <code>createChange()</code>. */ private ImportRewrite fImportRewriter; /** * True iff there are call sites for the constructor to be encapsulated located in binary * classes. */ private boolean fCallSitesInBinaryUnits; /** * <code>CompilationUnit</code> in which the factory is to be created. */ private CompilationUnit fFactoryCU; /** * The fully qualified name of the factory class. This is only used if invoked from a * refactoring script. */ private String fFactoryClassName; private int fConstructorVisibility= Modifier.PRIVATE; /** * Creates a new <code>IntroduceFactoryRefactoring</code> with the given selection on the given * compilation unit. * * @param cu the <code>ICompilationUnit</code> in which the user selection was made, or * <code>null</code> if invoked from scripting * @param selectionStart the start of the textual selection in <code>cu</code> * @param selectionLength the length of the textual selection in <code>cu</code> */ public IntroduceFactoryRefactoring(ICompilationUnit cu, int selectionStart, int selectionLength) { Assert.isTrue(selectionStart >= 0); Assert.isTrue(selectionLength >= 0); fSelectionStart= selectionStart; fSelectionLength= selectionLength; fCUHandle= cu; if (cu != null) initialize(); } public IntroduceFactoryRefactoring(JavaRefactoringArguments arguments, RefactoringStatus status) { this(null, 0, 0); RefactoringStatus initializeStatus= initialize(arguments); status.merge(initializeStatus); } private void initialize() { fCU= ASTCreator.createAST(fCUHandle, null); } /** * Finds and returns the <code>ASTNode</code> for the given source text selection, if it is an * entire constructor call or the class name portion of a constructor call or constructor * declaration, or null otherwise. * * @param unit The compilation unit in which the selection was made * @param offset The textual offset of the start of the selection * @param length The length of the selection in characters * @return ClassInstanceCreation or MethodDeclaration */ private ASTNode getTargetNode(ICompilationUnit unit, int offset, int length) { ASTNode node= ASTNodes.getNormalizedNode(NodeFinder.perform(fCU, offset, length)); if (node.getNodeType() == ASTNode.CLASS_INSTANCE_CREATION) return node; if (node.getNodeType() == ASTNode.METHOD_DECLARATION && ((MethodDeclaration) node).isConstructor()) return node; // we have some sub node. Make sure its the right child of the parent StructuralPropertyDescriptor location= node.getLocationInParent(); ASTNode parent= node.getParent(); if (location == ClassInstanceCreation.TYPE_PROPERTY) { return parent; } else if (location == MethodDeclaration.NAME_PROPERTY && ((MethodDeclaration) parent).isConstructor()) { return parent; } return null; } /** * Determines what kind of AST node was selected, and returns an error status if the kind of * node is inappropriate for this refactoring. * * @param pm * @return a RefactoringStatus indicating whether the selection is valid * @throws JavaModelException */ private RefactoringStatus checkSelection(IProgressMonitor pm) throws JavaModelException { try { pm.beginTask(RefactoringCoreMessages.IntroduceFactory_examiningSelection, 2); fSelectedNode= getTargetNode(fCUHandle, fSelectionStart, fSelectionLength); if (fSelectedNode == null) return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_notAConstructorInvocation); // getTargetNode() must return either a ClassInstanceCreation or a // constructor MethodDeclaration; nothing else. if (fSelectedNode instanceof ClassInstanceCreation) { ClassInstanceCreation classInstanceCreation= (ClassInstanceCreation) fSelectedNode; fCtorBinding= classInstanceCreation.resolveConstructorBinding(); } else if (fSelectedNode instanceof MethodDeclaration) { MethodDeclaration methodDeclaration= (MethodDeclaration) fSelectedNode; fCtorBinding= methodDeclaration.resolveBinding(); } if (fCtorBinding == null) return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_unableToResolveConstructorBinding); // If this constructor is of a generic type, get the generic version, // not some instantiation thereof. fCtorBinding= fCtorBinding.getMethodDeclaration(); pm.worked(1); // We don't handle constructors of nested types at the moment if (fCtorBinding.getDeclaringClass().isNested()) return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_unsupportedNestedTypes); ITypeBinding ctorType= fCtorBinding.getDeclaringClass(); IType ctorOwningType= (IType) ctorType.getJavaElement(); if (ctorOwningType.isBinary()) // Can't modify binary CU; don't know what CU to put factory method return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_constructorInBinaryClass); if (ctorOwningType.isEnum()) // Doesn't make sense to encapsulate enum constructors return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_constructorInEnum); // Put the generated factory method inside the type that owns the constructor fFactoryUnitHandle= ctorOwningType.getCompilationUnit(); fFactoryCU= getASTFor(fFactoryUnitHandle); Name ctorOwnerName= (Name) NodeFinder.perform(fFactoryCU, ctorOwningType.getNameRange()); fCtorOwningClass= (AbstractTypeDeclaration) ASTNodes.getParent(ctorOwnerName, AbstractTypeDeclaration.class); fFactoryOwningClass= fCtorOwningClass; pm.worked(1); if (fNewMethodName == null) return setNewMethodName("create" + fCtorBinding.getName());//$NON-NLS-1$ else return new RefactoringStatus(); } finally { pm.done(); } } /* * @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkActivation(org.eclipse.core.runtime.IProgressMonitor) * * CODINGSPECTATOR: Log the refactoring if it failed with fatal error while checking initial conditions. * */ @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException { try { pm.beginTask(RefactoringCoreMessages.IntroduceFactory_checkingActivation, 1); if (!fCUHandle.isStructureKnown()) { RefactoringStatus status= RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_syntaxError); //CODINGSPECTATOR logUnavailableRefactoring(status); return status; } RefactoringStatus status= checkSelection(new SubProgressMonitor(pm, 1)); //CODINGSPECTATOR if (status.hasFatalError()) { logUnavailableRefactoring(status); } return status; } finally { pm.done(); } } /** * @param searchHits * @return the set of compilation units that will be affected by this particular invocation of * this refactoring. This in general includes the class containing the constructor in * question, as well as all call sites to the constructor. */ private ICompilationUnit[] collectAffectedUnits(SearchResultGroup[] searchHits) { Collection<ICompilationUnit> result= new ArrayList<ICompilationUnit>(); boolean hitInFactoryClass= false; for (int i= 0; i < searchHits.length; i++) { SearchResultGroup rg= searchHits[i]; ICompilationUnit icu= rg.getCompilationUnit(); result.add(icu); if (icu.equals(fFactoryUnitHandle)) hitInFactoryClass= true; } if (!hitInFactoryClass) result.add(fFactoryUnitHandle); return result.toArray(new ICompilationUnit[result.size()]); } /** * @param ctor * @param methodBinding * @return a <code>SearchPattern</code> that finds all calls to the constructor identified by * the argument <code>methodBinding</code>. */ private SearchPattern createSearchPattern(IMethod ctor, IMethodBinding methodBinding) { Assert.isNotNull(methodBinding, RefactoringCoreMessages.IntroduceFactory_noBindingForSelectedConstructor); if (ctor != null) return SearchPattern.createPattern(ctor, IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE); else { // perhaps a synthetic method? (but apparently not always... hmmm...) // Can't find an IMethod for this method, so build a string pattern instead StringBuffer buf= new StringBuffer(); buf.append(methodBinding.getDeclaringClass().getQualifiedName()) .append("(");//$NON-NLS-1$ for (int i= 0; i < fArgTypes.length; i++) { if (i != 0) buf.append(","); //$NON-NLS-1$ buf.append(fArgTypes[i].getQualifiedName()); } buf.append(")"); //$NON-NLS-1$ return SearchPattern.createPattern(buf.toString(), IJavaSearchConstants.CONSTRUCTOR, IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE); } } private IJavaSearchScope createSearchScope(IMethod ctor, IMethodBinding binding) throws JavaModelException { if (ctor != null) { return RefactoringScopeFactory.create(ctor); } else { ITypeBinding type= Bindings.getTopLevelType(binding.getDeclaringClass()); return RefactoringScopeFactory.create(type.getJavaElement()); } } /** * @param groups * @return an array of <code>SearchResultGroup</code>'s like the argument, but omitting those * groups that have no corresponding compilation unit (i.e. are binary and therefore * can't be modified). */ private SearchResultGroup[] excludeBinaryUnits(SearchResultGroup[] groups) { Collection<SearchResultGroup> result= new ArrayList<SearchResultGroup>(); for (int i= 0; i < groups.length; i++) { SearchResultGroup rg= groups[i]; ICompilationUnit unit= rg.getCompilationUnit(); if (unit != null) // ignore hits within a binary unit result.add(rg); else fCallSitesInBinaryUnits= true; } return result.toArray(new SearchResultGroup[result.size()]); } /** * Search for all calls to the given <code>IMethodBinding</code> in the project that contains * the compilation unit <code>fCUHandle</code>. * * @param methodBinding * @param pm * @param status * @return an array of <code>SearchResultGroup</code>'s that identify the search matches * @throws JavaModelException */ private SearchResultGroup[] searchForCallsTo(IMethodBinding methodBinding, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException { IMethod method= (IMethod) methodBinding.getJavaElement(); final RefactoringSearchEngine2 engine= new RefactoringSearchEngine2(createSearchPattern(method, methodBinding)); engine.setFiltering(true, true); engine.setScope(createSearchScope(method, methodBinding)); engine.setStatus(status); engine.searchPattern(new SubProgressMonitor(pm, 1)); return (SearchResultGroup[]) engine.getResults(); } /** * Returns an array of <code>SearchResultGroup</code>'s containing all method calls in the Java * project that invoke the constructor identified by the given <code>IMethodBinding</code> * * @param ctorBinding an <code>IMethodBinding</code> identifying a particular constructor * signature to search for * @param pm an <code>IProgressMonitor</code> to use during this potentially lengthy operation * @param status * @return an array of <code>SearchResultGroup</code>'s identifying all calls to the given * constructor signature * @throws JavaModelException */ private SearchResultGroup[] findAllCallsTo(IMethodBinding ctorBinding, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException { SearchResultGroup[] groups= excludeBinaryUnits(searchForCallsTo(ctorBinding, pm, status)); return groups; } private IType findNonPrimaryType(String fullyQualifiedName, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException { SearchPattern p= SearchPattern.createPattern(fullyQualifiedName, IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE); final RefactoringSearchEngine2 engine= new RefactoringSearchEngine2(p); engine.setFiltering(true, true); engine.setScope(RefactoringScopeFactory.create(fCtorBinding.getJavaElement().getJavaProject())); engine.setStatus(status); engine.searchPattern(new SubProgressMonitor(pm, 1)); SearchResultGroup[] groups= (SearchResultGroup[]) engine.getResults(); if (groups.length != 0) { for (int i= 0; i < groups.length; i++) { SearchMatch[] matches= groups[i].getSearchResults(); for (int j= 0; j < matches.length; j++) { if (matches[j].getAccuracy() == SearchMatch.A_ACCURATE) return (IType) matches[j].getElement(); } } } return null; } /* * @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkInput(org.eclipse.core.runtime.IProgressMonitor) */ @Override public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException { try { pm.beginTask(RefactoringCoreMessages.IntroduceFactory_checking_preconditions, 1); RefactoringStatus result= new RefactoringStatus(); if (fFactoryClassName != null) result.merge(setFactoryClass(fFactoryClassName)); if (result.hasFatalError()) return result; fArgTypes= fCtorBinding.getParameterTypes(); fCtorIsVarArgs= fCtorBinding.isVarargs(); fAllCallsTo= findAllCallsTo(fCtorBinding, pm, result); fFormalArgNames= findCtorArgNames(); ICompilationUnit[] affectedFiles= collectAffectedUnits(fAllCallsTo); result.merge(Checks.validateModifiesFiles(ResourceUtil.getFiles(affectedFiles), getValidationContext())); if (fCallSitesInBinaryUnits) result.merge(RefactoringStatus.createWarningStatus(RefactoringCoreMessages.IntroduceFactory_callSitesInBinaryClass)); return result; } finally { pm.done(); } } /** * @return an array containing the argument names for the constructor identified by * <code>fCtorBinding</code>, if available, or default names if unavailable (e.g. if the * constructor resides in a binary unit). */ private String[] findCtorArgNames() { int numArgs= fCtorBinding.getParameterTypes().length; String[] names= new String[numArgs]; CompilationUnit ctorUnit= (CompilationUnit) ASTNodes.getParent(fCtorOwningClass, CompilationUnit.class); MethodDeclaration ctorDecl= (MethodDeclaration) ctorUnit.findDeclaringNode(fCtorBinding.getKey()); if (ctorDecl != null) { List<SingleVariableDeclaration> formalArgs= ctorDecl.parameters(); int i= 0; for (Iterator<SingleVariableDeclaration> iter= formalArgs.iterator(); iter.hasNext(); i++) { SingleVariableDeclaration svd= iter.next(); names[i]= svd.getName().getIdentifier(); } return names; } // Have no way of getting the formal argument names; just fake it. for (int i= 0; i < numArgs; i++) names[i]= "arg" + (i + 1); //$NON-NLS-1$ return names; } /** * Creates and returns a new MethodDeclaration that represents the factory method to be used in * place of direct calls to the constructor in question. * * @param ast An AST used as a factory for various AST nodes * @param ctorBinding binding for the constructor being wrapped * @param unitRewriter the ASTRewrite to be used * @return the new method declaration */ private MethodDeclaration createFactoryMethod(AST ast, IMethodBinding ctorBinding, ASTRewrite unitRewriter) { MethodDeclaration newMethod= ast.newMethodDeclaration(); SimpleName newMethodName= ast.newSimpleName(fNewMethodName); ClassInstanceCreation newCtorCall= ast.newClassInstanceCreation(); ReturnStatement ret= ast.newReturnStatement(); Block body= ast.newBlock(); List<Statement> stmts= body.statements(); String retTypeName= ctorBinding.getName(); createFactoryMethodSignature(ast, newMethod); newMethod.setName(newMethodName); newMethod.setBody(body); ITypeBinding[] ctorOwnerTypeParameters= fCtorBinding.getDeclaringClass().getTypeParameters(); setMethodReturnType(newMethod, retTypeName, ctorOwnerTypeParameters, ast); newMethod.modifiers().addAll(ASTNodeFactory.newModifiers(ast, Modifier.STATIC | Modifier.PUBLIC)); setCtorTypeArguments(newCtorCall, retTypeName, ctorOwnerTypeParameters, ast); createFactoryMethodConstructorArgs(ast, newCtorCall); ret.setExpression(newCtorCall); stmts.add(ret); return newMethod; } /** * Sets the type being instantiated in the given constructor call, including specifying any * necessary type arguments. * * @param newCtorCall the constructor call to modify * @param ctorTypeName the simple name of the type being instantiated * @param ctorOwnerTypeParameters the formal type parameters of the type being instantiated * @param ast utility object used to create AST nodes */ private void setCtorTypeArguments(ClassInstanceCreation newCtorCall, String ctorTypeName, ITypeBinding[] ctorOwnerTypeParameters, AST ast) { if (ctorOwnerTypeParameters.length == 0) // easy, just a simple type newCtorCall.setType(ASTNodeFactory.newType(ast, ctorTypeName)); else { Type baseType= ast.newSimpleType(ast.newSimpleName(ctorTypeName)); ParameterizedType newInstantiatedType= ast.newParameterizedType(baseType); List<Type> newInstTypeArgs= newInstantiatedType.typeArguments(); for (int i= 0; i < ctorOwnerTypeParameters.length; i++) { Type typeArg= ASTNodeFactory.newType(ast, ctorOwnerTypeParameters[i].getName()); newInstTypeArgs.add(typeArg); } newCtorCall.setType(newInstantiatedType); } } /** * Sets the return type of the factory method, including any necessary type arguments. E.g., for * constructor <code>Foo()</code> in <code>Foo<T></code>, the factory method defines a * method type parameter <code><T></code> and returns a <code>Foo<T></code>. * * @param newMethod the method whose return type is to be set * @param retTypeName the simple name of the return type (without type parameters) * @param ctorOwnerTypeParameters the formal type parameters of the type that the factory method * instantiates (whose constructor is being encapsulated) * @param ast utility object used to create AST nodes */ private void setMethodReturnType(MethodDeclaration newMethod, String retTypeName, ITypeBinding[] ctorOwnerTypeParameters, AST ast) { if (ctorOwnerTypeParameters.length == 0) newMethod.setReturnType2(ast.newSimpleType(ast.newSimpleName(retTypeName))); else { Type baseType= ast.newSimpleType(ast.newSimpleName(retTypeName)); ParameterizedType newRetType= ast.newParameterizedType(baseType); List<Type> newRetTypeArgs= newRetType.typeArguments(); for (int i= 0; i < ctorOwnerTypeParameters.length; i++) { Type retTypeArg= ASTNodeFactory.newType(ast, ctorOwnerTypeParameters[i].getName()); newRetTypeArgs.add(retTypeArg); } newMethod.setReturnType2(newRetType); } } /** * Creates and adds the necessary argument declarations to the given factory method.<br> * An argument is needed for each original constructor argument for which the evaluation of the * actual arguments across all calls was not able to be pushed inside the factory method (e.g. * arguments with side-effects, references to fields if the factory method is to be static or * reside in a factory class, or arguments that varied across the set of constructor calls).<br> * <code>fArgTypes</code> identifies such arguments by a <code>null</code> value. * * @param ast utility object used to create AST nodes * @param newMethod the <code>MethodDeclaration</code> for the factory method */ private void createFactoryMethodSignature(AST ast, MethodDeclaration newMethod) { List<SingleVariableDeclaration> argDecls= newMethod.parameters(); for (int i= 0; i < fArgTypes.length; i++) { SingleVariableDeclaration argDecl= ast.newSingleVariableDeclaration(); Type argType; if (i == (fArgTypes.length - 1) && fCtorIsVarArgs) { // The trailing varargs arg has an extra array dimension, compared to // what we need to pass to setType()... argType= typeNodeForTypeBinding(fArgTypes[i].getElementType(), fArgTypes[i].getDimensions() - 1, ast); argDecl.setVarargs(true); } else argType= typeNodeForTypeBinding(fArgTypes[i], 0, ast); argDecl.setName(ast.newSimpleName(fFormalArgNames[i])); argDecl.setType(argType); argDecls.add(argDecl); } ITypeBinding[] ctorExcepts= fCtorBinding.getExceptionTypes(); List<Name> exceptions= newMethod.thrownExceptions(); for (int i= 0; i < ctorExcepts.length; i++) { String excName= fImportRewriter.addImport(ctorExcepts[i]); exceptions.add(ASTNodeFactory.newName(ast, excName)); } copyTypeParameters(ast, newMethod); } /** * Copies the constructor's parent type's type parameters, if any, as method type parameters of * the new static factory method. (Recall that static methods can't refer to type arguments of * the enclosing class, since they have no instance to serve as a context.)<br> * Makes sure to copy the bounds from the owning type, to ensure that the return type of the * factory method satisfies the bounds of the type being instantiated.<br> * E.g., for ctor Foo() in the type Foo<T extends Number>, be sure that the factory method is * declared as<br> * <code>static <T extends Number> Foo<T> createFoo()</code><br> * and not simply<br> * <code>static <T> Foo<T> createFoo()</code><br> * or the compiler will bark. * * @param ast utility object needed to create ASTNode's for the new method * @param newMethod the method onto which to copy the type parameters */ private void copyTypeParameters(AST ast, MethodDeclaration newMethod) { ITypeBinding[] ctorOwnerTypeParms= fCtorBinding.getDeclaringClass().getTypeParameters(); List<TypeParameter> factoryMethodTypeParms= newMethod.typeParameters(); for (int i= 0; i < ctorOwnerTypeParms.length; i++) { TypeParameter newParm= ast.newTypeParameter(); ITypeBinding[] parmTypeBounds= ctorOwnerTypeParms[i].getTypeBounds(); List<Type> newParmBounds= newParm.typeBounds(); newParm.setName(ast.newSimpleName(ctorOwnerTypeParms[i].getName())); for (int b= 0; b < parmTypeBounds.length; b++) { if (parmTypeBounds[b].isClass() && parmTypeBounds[b].getSuperclass() == null) continue; Type newBound= fImportRewriter.addImport(parmTypeBounds[b], ast); newParmBounds.add(newBound); } factoryMethodTypeParms.add(newParm); } } /** * @param argType * @param extraDims number of extra array dimensions to add to the resulting type * @param ast * @return a Type that describes the given ITypeBinding. If the binding refers to an object * type, use the import rewriter to determine whether the reference requires a new * import, or instead needs to be qualified.<br> * Like ASTNodeFactory.newType(), but for the handling of imports. */ private Type typeNodeForTypeBinding(ITypeBinding argType, int extraDims, AST ast) { if (extraDims > 0) { return ast.newArrayType(typeNodeForTypeBinding(argType, 0, ast), extraDims); } else if (argType.isArray()) { Type elementType= typeNodeForTypeBinding(argType.getElementType(), extraDims, ast); return ast.newArrayType(elementType, argType.getDimensions()); } else { return fImportRewriter.addImport(argType, ast); } } /** * Create the list of actual arguments to the constructor call that is encapsulated inside the * factory method, and associate the arguments with the given constructor call object. * * @param ast utility object used to create AST nodes * @param newCtorCall the newly-generated constructor call to be wrapped inside the factory * method */ private void createFactoryMethodConstructorArgs(AST ast, ClassInstanceCreation newCtorCall) { List<Expression> argList= newCtorCall.arguments(); for (int i= 0; i < fArgTypes.length; i++) { ASTNode ctorArg= ast.newSimpleName(fFormalArgNames[i]); argList.add((Expression) ctorArg); } } /** * Updates the constructor call. * * @param ctorCall the ClassInstanceCreation to be marked as replaced * @param unitRewriter the AST rewriter * @param gd the edit group to use */ private void rewriteFactoryMethodCall(ClassInstanceCreation ctorCall, ASTRewrite unitRewriter, TextEditGroup gd) { AST ast= unitRewriter.getAST(); MethodInvocation factoryMethodCall= ast.newMethodInvocation(); ASTNode ctorCallParent= ctorCall.getParent(); StructuralPropertyDescriptor ctorCallLocation= ctorCall.getLocationInParent(); if (ctorCallLocation instanceof ChildListPropertyDescriptor) { ListRewrite ctorCallParentListRewrite= unitRewriter.getListRewrite(ctorCallParent, (ChildListPropertyDescriptor) ctorCallLocation); int index= ctorCallParentListRewrite.getOriginalList().indexOf(ctorCall); ctorCall= (ClassInstanceCreation) ctorCallParentListRewrite.getRewrittenList().get(index); } else { ctorCall= (ClassInstanceCreation) unitRewriter.get(ctorCallParent, ctorCallLocation); } ListRewrite actualFactoryArgs= unitRewriter.getListRewrite(factoryMethodCall, MethodInvocation.ARGUMENTS_PROPERTY); ListRewrite actualCtorArgs= unitRewriter.getListRewrite(ctorCall, ClassInstanceCreation.ARGUMENTS_PROPERTY); // Need to use a qualified name for the factory method if we're not // in the context of the class holding the factory. AbstractTypeDeclaration callOwner= (AbstractTypeDeclaration) ASTNodes.getParent(ctorCall, AbstractTypeDeclaration.class); ITypeBinding callOwnerBinding= callOwner.resolveBinding(); if (callOwnerBinding == null || !Bindings.equals(callOwner.resolveBinding(), fFactoryOwningClass.resolveBinding())) { String qualifier= fImportRewriter.addImport(fFactoryOwningClass.resolveBinding()); factoryMethodCall.setExpression(ASTNodeFactory.newName(ast, qualifier)); } factoryMethodCall.setName(ast.newSimpleName(fNewMethodName)); List<Expression> actualCtorArgsList= actualCtorArgs.getRewrittenList(); for (int i= 0; i < actualCtorArgsList.size(); i++) { Expression actualCtorArg= actualCtorArgsList.get(i); ASTNode movedArg; if (ASTNodes.isExistingNode(actualCtorArg)) { movedArg= unitRewriter.createMoveTarget(actualCtorArg); } else { unitRewriter.remove(actualCtorArg, null); movedArg= actualCtorArg; } actualFactoryArgs.insertLast(movedArg, gd); } unitRewriter.replace(ctorCall, factoryMethodCall, gd); } /** * @param unit * @return true iff the given <code>ICompilationUnit</code> is the unit containing the original * constructor */ private boolean isConstructorUnit(ICompilationUnit unit) { return unit.equals(ASTCreator.getCu(fCtorOwningClass)); } /** * @return true iff we should actually change the original constructor's visibility to * <code>protected</code>. This takes into account the user- requested mode and whether * the constructor's compilation unit is in source form. */ private boolean shouldProtectConstructor() { return fProtectConstructor && fCtorOwningClass != null; } /** * Creates and adds the necessary change to make the constructor method protected. * * @param unitAST * @param unitRewriter * @param declGD * @return false iff the constructor didn't exist (i.e. was implicit) */ private boolean protectConstructor(CompilationUnit unitAST, ASTRewrite unitRewriter, TextEditGroup declGD) { MethodDeclaration constructor= (MethodDeclaration) unitAST.findDeclaringNode(fCtorBinding.getKey()); // No need to rewrite the modifiers if the visibility is what we already want it to be. if (constructor == null || (JdtFlags.getVisibilityCode(constructor)) == fConstructorVisibility) return false; ModifierRewrite.create(unitRewriter, constructor).setVisibility(fConstructorVisibility, declGD); return true; } /** * Add all changes necessary on the <code>ICompilationUnit</code> in the given * <code>SearchResultGroup</code> to implement the refactoring transformation to the given * <code>CompilationUnitChange</code>. * * @param rg the <code>SearchResultGroup</code> for which changes should be created * @param unitHandle * @param unitChange the CompilationUnitChange object for the compilation unit in question * @return <code>true</code> iff a change has been added * @throws CoreException */ private boolean addAllChangesFor(SearchResultGroup rg, ICompilationUnit unitHandle, CompilationUnitChange unitChange) throws CoreException { // ICompilationUnit unitHandle= rg.getCompilationUnit(); Assert.isTrue(rg == null || rg.getCompilationUnit() == unitHandle); CompilationUnit unit= getASTFor(unitHandle); ASTRewrite unitRewriter= ASTRewrite.create(unit.getAST()); MultiTextEdit root= new MultiTextEdit(); boolean someChange= false; unitChange.setEdit(root); fImportRewriter= StubUtility.createImportRewrite(unit, true); // First create the factory method if (unitHandle.equals(fFactoryUnitHandle)) { TextEditGroup factoryGD= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_addFactoryMethod); createFactoryChange(unitRewriter, unit, factoryGD); unitChange.addTextEditGroup(factoryGD); someChange= true; } // Now rewrite all the constructor calls to use the factory method if (rg != null) if (replaceConstructorCalls(rg, unit, unitRewriter, unitChange)) someChange= true; // Finally, make the constructor private, if requested. if (shouldProtectConstructor() && isConstructorUnit(unitHandle)) { TextEditGroup declGD= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_protectConstructor); if (protectConstructor(unit, unitRewriter, declGD)) { unitChange.addTextEditGroup(declGD); someChange= true; } } if (someChange) { root.addChild(unitRewriter.rewriteAST()); root.addChild(fImportRewriter.rewriteImports(null)); } return someChange; } /** * @param unitHandle * @return an AST for the given compilation unit handle.<br> * If this is the unit containing the selection or the unit in which the factory is to * reside, checks the appropriate field (<code>fCU</code> or <code>fFactoryCU</code>, * respectively) and initializes the field with a new AST only if not already done. */ private CompilationUnit getASTFor(ICompilationUnit unitHandle) { if (unitHandle.equals(fCUHandle)) { // is this the unit containing the selection? if (fCU == null) { fCU= ASTCreator.createAST(unitHandle, null); if (fCU.equals(fFactoryUnitHandle)) // if selection unit and factory unit are the same... fFactoryCU= fCU; // ...make sure the factory unit gets initialized } return fCU; } else if (unitHandle.equals(fFactoryUnitHandle)) { // is this the "factory unit"? if (fFactoryCU == null) fFactoryCU= ASTCreator.createAST(unitHandle, null); return fFactoryCU; } else return ASTCreator.createAST(unitHandle, null); } /** * Use the given <code>ASTRewrite</code> to replace direct calls to the constructor with calls * to the newly-created factory method. * * @param rg the <code>SearchResultGroup</code> indicating all of the constructor references * @param unit the <code>CompilationUnit</code> to be rewritten * @param unitRewriter the rewriter * @param unitChange the compilation unit change * @throws CoreException * @return true iff at least one constructor call site was rewritten. */ private boolean replaceConstructorCalls(SearchResultGroup rg, CompilationUnit unit, ASTRewrite unitRewriter, CompilationUnitChange unitChange) throws CoreException { Assert.isTrue(ASTCreator.getCu(unit).equals(rg.getCompilationUnit())); SearchMatch[] hits= rg.getSearchResults(); Arrays.sort(hits, new Comparator<SearchMatch>() { /** * Sort by descending offset, such that nested constructor calls are processed first. * This is necessary, since they can only be moved into the factory method invocation * after they have been rewritten. */ public int compare(SearchMatch m1, SearchMatch m2) { return m2.getOffset() - m1.getOffset(); } }); boolean someCallPatched= false; for (int i= 0; i < hits.length; i++) { ASTNode ctrCall= getCtorCallAt(hits[i].getOffset(), hits[i].getLength(), unit); if (ctrCall instanceof ClassInstanceCreation) { TextEditGroup gd= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_replaceCalls); rewriteFactoryMethodCall((ClassInstanceCreation) ctrCall, unitRewriter, gd); unitChange.addTextEditGroup(gd); someCallPatched= true; } else if (ctrCall instanceof MethodRef) { TextEditGroup gd= new TextEditGroup(RefactoringCoreMessages.IntroduceFactoryRefactoring_replaceJavadocReference); rewriteJavadocReference((MethodRef) ctrCall, unitRewriter, gd); unitChange.addTextEditGroup(gd); someCallPatched= true; } } return someCallPatched; } private void rewriteJavadocReference(MethodRef javadocRef, ASTRewrite unitRewriter, TextEditGroup gd) { AST ast= unitRewriter.getAST(); unitRewriter.replace(javadocRef.getName(), ast.newSimpleName(fNewMethodName), gd); } /** * Look "in the vicinity" of the given range to find the <code>ClassInstanceCreation</code> node * that this search hit identified. Necessary because the <code>SearchEngine</code> doesn't * always cough up text extents that <code>NodeFinder.perform()</code> agrees with. * * @param start * @param length * @param unitAST * @return return a {@link ClassInstanceCreation} or a {@link MethodRef} or <code>null</code> if * this is really a constructor->constructor call (e.g. "this(...)") * @throws CoreException */ private ASTNode getCtorCallAt(int start, int length, CompilationUnit unitAST) throws CoreException { ICompilationUnit unitHandle= ASTCreator.getCu(unitAST); ASTNode node= NodeFinder.perform(unitAST, start, length); if (node == null) throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, Messages.format(RefactoringCoreMessages.IntroduceFactory_noASTNodeForConstructorSearchHit, new Object[] { Integer.toString(start), Integer.toString(start + length), BasicElementLabels.getJavaCodeString(unitHandle.getSource().substring(start, start + length)), BasicElementLabels.getFileName(unitHandle) }), null)); if (node instanceof ClassInstanceCreation) { if (((ClassInstanceCreation) node).getAnonymousClassDeclaration() != null) { // Cannot replace anonymous inner class, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=250660 fConstructorVisibility= Modifier.PROTECTED; return null; } return node; } else if (node instanceof VariableDeclaration) { Expression init= ((VariableDeclaration) node).getInitializer(); if (init instanceof ClassInstanceCreation) { return init; } else if (init != null) throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedInitializerNodeType, new Object[] { BasicElementLabels.getJavaCodeString(init.toString()), BasicElementLabels.getFileName(unitHandle) }), null)); else throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, Messages.format(RefactoringCoreMessages.IntroduceFactory_noConstructorCallNodeInsideFoundVarbleDecl, BasicElementLabels.getJavaCodeString(node.toString())), null)); } else if (node instanceof ConstructorInvocation) { // This is a call we can bypass; it's from one constructor flavor // to another flavor on the same class. return null; } else if (node instanceof SuperConstructorInvocation) { // This is a call we can bypass; it's from one constructor flavor // to another flavor on the same class. fConstructorVisibility= Modifier.PROTECTED; return null; } else if (node instanceof ExpressionStatement) { Expression expr= ((ExpressionStatement) node).getExpression(); if (expr instanceof ClassInstanceCreation) return expr; else throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedASTNodeTypeForConstructorSearchHit, new Object[] { BasicElementLabels.getJavaCodeString(expr.toString()), BasicElementLabels.getFileName(unitHandle) }), null)); } else if (node instanceof SimpleName && (node.getParent() instanceof MethodDeclaration || node.getParent() instanceof AbstractTypeDeclaration)) { // We seem to have been given a hit for an implicit call to the base-class constructor. // Do nothing with this (implicit) call, but have to make sure we make the derived class // doesn't lose access to the base-class constructor (so make it 'protected', not 'private'). fConstructorVisibility= Modifier.PROTECTED; return null; } else if (node instanceof MethodRef) { return node; } else throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedASTNodeTypeForConstructorSearchHit, new Object[] { BasicElementLabels.getJavaElementName(node.getClass().getName() + "('" + node.toString() + "')"), BasicElementLabels.getFileName(unitHandle) }), //$NON-NLS-1$ //$NON-NLS-2$ null)); } /** * Perform the AST rewriting necessary on the given <code>CompilationUnit</code> to create the * factory method. The method will reside on the type identified by * <code>fFactoryOwningClass</code>. * * @param unitRewriter * @param unit * @param gd the <code>GroupDescription</code> to associate with the changes made */ private void createFactoryChange(ASTRewrite unitRewriter, CompilationUnit unit, TextEditGroup gd) { // ================================================================================ // First add the factory itself (method, class, and interface as needed/directed by user) AST ast= unit.getAST(); fFactoryMethod= createFactoryMethod(ast, fCtorBinding, unitRewriter); AbstractTypeDeclaration factoryOwner= (AbstractTypeDeclaration) unit.findDeclaringNode(fFactoryOwningClass.resolveBinding().getKey()); fImportRewriter.addImport(fCtorOwningClass.resolveBinding()); int idx= ASTNodes.getInsertionIndex(fFactoryMethod, factoryOwner.bodyDeclarations()); if (idx < 0) idx= 0; // Guard against bug in getInsertionIndex() unitRewriter.getListRewrite(factoryOwner, factoryOwner.getBodyDeclarationsProperty()).insertAt(fFactoryMethod, idx, gd); } /* (non-Javadoc) * @see org.eclipse.ltk.core.refactoring.Refactoring#createChange(org.eclipse.core.runtime.IProgressMonitor) * * CODINGSPECTATOR: Extracted createRefactoringDescriptor from this method. */ @Override public Change createChange(IProgressMonitor pm) throws CoreException { try { pm.beginTask(RefactoringCoreMessages.IntroduceFactory_createChanges, fAllCallsTo.length); final DynamicValidationStateChange result= new DynamicValidationRefactoringChange(createRefactoringDescriptor(), RefactoringCoreMessages.IntroduceFactory_name); boolean hitInFactoryClass= false; boolean hitInCtorClass= false; for (int i= 0; i < fAllCallsTo.length; i++) { SearchResultGroup rg= fAllCallsTo[i]; ICompilationUnit unitHandle= rg.getCompilationUnit(); CompilationUnitChange cuChange= new CompilationUnitChange(getName(), unitHandle); if (addAllChangesFor(rg, unitHandle, cuChange)) result.add(cuChange); if (unitHandle.equals(fFactoryUnitHandle)) hitInFactoryClass= true; if (unitHandle.equals(ASTCreator.getCu(fCtorOwningClass))) hitInCtorClass= true; pm.worked(1); if (pm.isCanceled()) throw new OperationCanceledException(); } if (!hitInFactoryClass) { // Handle factory class if no search hits there CompilationUnitChange cuChange= new CompilationUnitChange(getName(), fFactoryUnitHandle); addAllChangesFor(null, fFactoryUnitHandle, cuChange); result.add(cuChange); } if (!hitInCtorClass && !fFactoryUnitHandle.equals(ASTCreator.getCu(fCtorOwningClass))) { // Handle constructor-owning class if no search hits there CompilationUnitChange cuChange= new CompilationUnitChange(getName(), ASTCreator.getCu(fCtorOwningClass)); addAllChangesFor(null, ASTCreator.getCu(fCtorOwningClass), cuChange); result.add(cuChange); } return result; } finally { pm.done(); } } /* (non-Javadoc) * @see org.eclipse.ltk.core.refactoring.Refactoring#getName() */ @Override public String getName() { return RefactoringCoreMessages.IntroduceFactory_name; } /** * Returns the name to be used for the generated factory method. * * @return the new method name */ public String getNewMethodName() { return fNewMethodName; } /** * Sets the name to be used for the generated factory method.<br> * Returns a <code>RefactoringStatus</code> that indicates whether the given name is valid for * the new factory method. * * @param newMethodName the name to be used for the generated factory method * @return the resulting status */ public RefactoringStatus setNewMethodName(String newMethodName) { Assert.isNotNull(newMethodName); fNewMethodName= newMethodName; RefactoringStatus stat= Checks.checkMethodName(newMethodName, fCUHandle); stat.merge(isUniqueMethodName(newMethodName)); return stat; } /** * @param methodName * @return a <code>RefactoringStatus</code> that identifies whether the the name * <code>newMethodName</code> is available to use as the name of the new factory method * within the factory-owner class (either a to-be- created factory class or the * constructor-owning class, depending on the user options). */ private RefactoringStatus isUniqueMethodName(String methodName) { ITypeBinding declaringClass= fCtorBinding.getDeclaringClass(); if (Bindings.findMethodInType(declaringClass, methodName, fCtorBinding.getParameterTypes()) != null) { String format= Messages.format(RefactoringCoreMessages.IntroduceFactory_duplicateMethodName, BasicElementLabels.getJavaElementName(methodName)); return RefactoringStatus.createErrorStatus(format); } return new RefactoringStatus(); } /** * Returns true iff the selected constructor can be protected. * * @return return <code>true</code> if the constructor can be made protected */ public boolean canProtectConstructor() { return !fCtorBinding.isSynthetic() && fFactoryCU.findDeclaringNode(fCtorBinding.getKey()) != null; } /** * If the argument is true, change the visibility of the constructor to <code>protected</code>, * thereby encapsulating it. * * @param protectConstructor */ public void setProtectConstructor(boolean protectConstructor) { fProtectConstructor= protectConstructor; } /** * Returns the project on behalf of which this refactoring was invoked. * * @return returns the Java project */ public IJavaProject getProject() { return fCUHandle.getJavaProject(); } /** * Sets the class on which the generated factory method is to be placed. * * @param fullyQualifiedTypeName an <code>IType</code> referring to an existing class * @return return the resulting status */ public RefactoringStatus setFactoryClass(String fullyQualifiedTypeName) { IType factoryType; try { factoryType= findFactoryClass(fullyQualifiedTypeName); if (factoryType == null) return RefactoringStatus.createErrorStatus(Messages.format(RefactoringCoreMessages.IntroduceFactory_noSuchClass, BasicElementLabels.getJavaElementName(fullyQualifiedTypeName))); if (factoryType.isAnnotation()) return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryMethodOnAnnotation); if (factoryType.isInterface()) return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryMethodOnInterface); } catch (JavaModelException e) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantCheckForInterface); } ICompilationUnit factoryUnitHandle= factoryType.getCompilationUnit(); if (factoryType.isBinary()) return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryInBinaryClass); else { try { if (!fFactoryUnitHandle.equals(factoryUnitHandle)) { fFactoryCU= getASTFor(factoryUnitHandle); fFactoryUnitHandle= factoryUnitHandle; } fFactoryOwningClass= (AbstractTypeDeclaration) ASTNodes.getParent(NodeFinder.perform(fFactoryCU, factoryType.getNameRange()), AbstractTypeDeclaration.class); String factoryPkg= factoryType.getPackageFragment().getElementName(); String ctorPkg= fCtorOwningClass.resolveBinding().getPackage().getName(); if (!factoryPkg.equals(ctorPkg)) fConstructorVisibility= Modifier.PUBLIC; else if (fFactoryOwningClass != fCtorOwningClass) fConstructorVisibility= 0; // No such thing as Modifier.PACKAGE... if (fFactoryOwningClass != fCtorOwningClass) fConstructorVisibility= 0; // No such thing as Modifier.PACKAGE... } catch (JavaModelException e) { return RefactoringStatus.createFatalErrorStatus(e.getMessage()); } return new RefactoringStatus(); } } /** * Finds the factory class associated with the fully qualified name. * * @param fullyQualifiedTypeName the fully qualified type name * @return the factory class, or <code>null</code> if not found * @throws JavaModelException if an error occurs while finding the factory class */ private IType findFactoryClass(String fullyQualifiedTypeName) throws JavaModelException { IType factoryType= getProject().findType(fullyQualifiedTypeName); if (factoryType == null) // presumably a non-primary type; try the search engine factoryType= findNonPrimaryType(fullyQualifiedTypeName, new NullProgressMonitor(), new RefactoringStatus()); return factoryType; } /** * Returns the name of the class on which the generated factory method is to be placed. * * @return return the factory class name */ public String getFactoryClassName() { return fFactoryOwningClass.resolveBinding().getQualifiedName(); } private RefactoringStatus initialize(JavaRefactoringArguments arguments) { final String selection= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION); if (selection != null) { int offset= -1; int length= -1; final StringTokenizer tokenizer= new StringTokenizer(selection); if (tokenizer.hasMoreTokens()) offset= Integer.valueOf(tokenizer.nextToken()).intValue(); if (tokenizer.hasMoreTokens()) length= Integer.valueOf(tokenizer.nextToken()).intValue(); if (offset >= 0 && length >= 0) { fSelectionStart= offset; fSelectionLength= length; } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { selection, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION })); } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION)); String handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT); if (handle != null) { final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false); if (element == null || !element.exists() || element.getElementType() != IJavaElement.COMPILATION_UNIT) return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.INTRODUCE_FACTORY); else { fCUHandle= (ICompilationUnit) element; initialize(); } } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT)); handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1); if (handle != null) { final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false); if (element == null || !element.exists() || element.getElementType() != IJavaElement.TYPE) return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.INTRODUCE_FACTORY); else { final IType type= (IType) element; fFactoryClassName= type.getFullyQualifiedName(); } } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT)); final String name= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME); if (name != null && !"".equals(name)) //$NON-NLS-1$ fNewMethodName= name; else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME)); final String protect= arguments.getAttribute(ATTRIBUTE_PROTECT); if (protect != null) { fProtectConstructor= Boolean.valueOf(protect).booleanValue(); } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_PROTECT)); return new RefactoringStatus(); } ///////////////// //CODINGSPECTATOR ///////////////// //Extracted from createChange. private IntroduceFactoryDescriptor createRefactoringDescriptor() { final ITypeBinding binding= fFactoryOwningClass.resolveBinding(); final Map<String, String> arguments= new HashMap<String, String>(); String project= null; IJavaProject javaProject= fCUHandle.getJavaProject(); if (javaProject != null) project= javaProject.getElementName(); int flags= JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE; if (binding.isNested() && !binding.isMember()) flags|= JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT; final String description= Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_descriptor_description_short, BasicElementLabels.getJavaElementName(fCtorOwningClass.getName().getIdentifier())); final String header= Messages.format(RefactoringCoreMessages.IntroduceFactory_descriptor_description, new String[] { BasicElementLabels.getJavaElementName(fNewMethodName), BindingLabelProvider.getBindingLabel(binding, JavaElementLabels.ALL_FULLY_QUALIFIED), BindingLabelProvider.getBindingLabel(fCtorBinding, JavaElementLabels.ALL_FULLY_QUALIFIED) }); final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header); comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_original_pattern, BindingLabelProvider.getBindingLabel(fCtorBinding, JavaElementLabels.ALL_FULLY_QUALIFIED))); comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_factory_pattern, BasicElementLabels.getJavaElementName(fNewMethodName))); comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_owner_pattern, BindingLabelProvider.getBindingLabel(binding, JavaElementLabels.ALL_FULLY_QUALIFIED))); if (fProtectConstructor) comment.addSetting(RefactoringCoreMessages.IntroduceFactoryRefactoring_declare_private); final IntroduceFactoryDescriptor descriptor= RefactoringSignatureDescriptorFactory.createIntroduceFactoryDescriptor(project, description, comment.asString(), arguments, flags); arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT, JavaRefactoringDescriptorUtil.elementToHandle(project, fCUHandle)); arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME, fNewMethodName); arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1, JavaRefactoringDescriptorUtil.elementToHandle(project, binding.getJavaElement())); arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION, new Integer(fSelectionStart).toString() + " " + new Integer(fSelectionLength).toString()); //$NON-NLS-1$ arguments.put(ATTRIBUTE_PROTECT, Boolean.valueOf(fProtectConstructor).toString()); return descriptor; } protected RefactoringDescriptor getOriginalRefactoringDescriptor() { return createRefactoringDescriptor(); } protected String getDescriptorID() { return IJavaRefactorings.INTRODUCE_FACTORY; } }